#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         TimeTrendReport.py
#
# Copyright:    Copyright (c) 2005, Faulkner Technologies
#
# Author:       Darcy Clark
#
# Description:  This report generates a report that is equivalent to
#               a Summary report with data split over a specified
#               periodicity (quarterly, monthly etc..)
#
# See Also:     GenericObject, Component, Model, Section
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.MultipleAssessmentReport import MultipleAssessmentReport, MultipleAssessmentReportInfo
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.EventDefinition import *
from Products.KnowledgeEngine.WizardStepDefinition import *
from Products.KnowledgeEngine.AssessmentFilter import AssessmentFilter
from Products.KnowledgeEngine.KERLProcessor import *

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from App.ImageFile import ImageFile
from DateTime import DateTime

#####################################################################
## Python Library Imports
from string import capitalize
from calendar import monthrange

#####################################################################
## TimeTrendReportInfo Class Definition
class TimeTrendReportInfo(MultipleAssessmentReportInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """
    
    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Returns the name of the class for which 
            we are providing information.
        """
        return 'TimeTrendReport'
        
    #---------------------------------------------------------------#
    
    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Time and Trend Report"
        
    #---------------------------------------------------------------#
    
    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "TimeTrendReport"
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('skins/TimeTrendReport_icon.gif', globals())
    
    #---------------------------------------------------------------#
    
    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
        
        #initialise the super-class properties for reflection
        MultipleAssessmentReportInfo.onConfigureProperties(self)
    
    #---------------------------------------------------------------#
    
    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific events using the EventReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
                
        #initialise the super-class events for reflection
        MultipleAssessmentReportInfo.onConfigureEvents(self)
        
    #---------------------------------------------------------------#
    
#####################################################################
## TimeTrendReport Class Definition
class TimeTrendReport(MultipleAssessmentReport):
    """
        A class to represent a report consisting of graphs of
        component state
    """
    
    #---------------------------------------------------------------#
    
    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = TimeTrendReportInfo()
    
    #---------------------------------------------------------------#
    
    def __init__(self, id, identity):
        "Constructor for the object"        
        MultipleAssessmentReport.__init__(self, id, identity)
        
    #---------------------------------------------------------------#
    
    def renderReportFor(self, genericObject, request, wizardState, baseAssessmentFilter, **args):
        """
            Renders the report content for the specified generic object
            and the type of report
        """

        #get the assessment storage service... we'll need that to manage the assessment
        assessmentStorageService = getAssessmentStorageService(self)

        #get the current user that is rendering the report
        user = request["USER"]

        #add the specified genericObject to the request
        request['GENERICOBJECT'] = genericObject
        
        #get trend time definition
        dateFrom = wizardState.getPropertyValue('dateFrom')
        periodSize = wizardState.getPropertyValue('periodSize')
        nrPeriods = wizardState.getPropertyValue('nrPeriods')
        
        #generate a list of period start dates
        periodStartDates = [dateFrom]
        lastPeriodStartDate = dateFrom
        for period in range(0, nrPeriods):
            if periodSize == 'weekly':
                nextPeriodStartDate =  lastPeriodStartDate + 7
        
            elif periodSize == 'monthly':
                nextPeriodStartDate =  monthAdd(lastPeriodStartDate, 1, dateFrom.day())

            elif periodSize == 'quarterly':
                nextPeriodStartDate =  monthAdd(lastPeriodStartDate, 4, dateFrom.day())

            elif periodSize == 'yearly':
                nextPeriodStartDate =  monthAdd(lastPeriodStartDate, 12, dateFrom.day())
                
            periodStartDates.append(nextPeriodStartDate)
            lastPeriodStartDate = nextPeriodStartDate
        
        #### generate a sequence of assessment filters to feed to component renderers
        assessmentFilters = []
        nrAssessmentsPerFilter = []
        for i in range(0, len(periodStartDates)-1):
            #build KERL query for all assessments before time[i]
            q1 = parseKERL("['assessment', 'createdafter', '%s']" % periodStartDates[i])
            
            #build KERL query for all assessments after time[i+1]
            q2 = parseKERL("['assessment', 'createdbefore', '%s']" % periodStartDates[i+1])
            
            #construct a new assessmentFilter combining q1, q2 and base assessmentFilter
            filterTitle = capitalize(periodSize[:-2]) + ' ' + str(i + 1) + ' (' + periodStartDates[i].strftime("%Y-%m-%d") + ' to ' + (periodStartDates[i+1]-1).strftime("%Y-%m-%d") + ')'
            filter = AssessmentFilter(title=filterTitle, expression=simplifyKERL(['all', baseAssessmentFilter.getExpression(), q1, q2]), model=self.getModel(), assessmentScope=baseAssessmentFilter.getAssessmentScope())
            
            #add assessmentFilter to sequence
            assessmentFilters.append(filter)
            
            #add nrAssessments per filter to sequence
            nrAssessments = assessmentStorageService.getNrAssessmentsSatisfyingFilter(filter, user)   
            nrAssessmentsPerFilter.append(nrAssessments)
        
        #### put sequences into request
        request['ASSESSMENT_FILTERS'] = assessmentFilters
        request['NR_ASSESSMENT_FILTERS'] = nrAssessmentsPerFilter
 
        #dynamically determine the report page template for the specified object
        return eval("self.%s_%s(self, REQUEST=request, **args)" % (genericObject.getClassInfo().getClassName(), self.getClassInfo().getClassName()))
        
    #---------------------------------------------------------------#
        
    def getWizardStepDefinitions(self, context=None):
        """
            This method is used by this class to
            determine the actual wizard step definitions
            for the wizard
            
            RETURN: [WizardStepDefinition]
        """
        
        #a function to determine if an object should be displayed in the tree 
        isDisplayableFunction = lambda genericObject: genericObject.isInstanceOf("Component") or genericObject.isInstanceOf("Model")
        
        #a function to determine if an object can be selected in the tree 
        isSelectableFunction = lambda genericObject: genericObject.isInstanceOf("CheckBox") or \
                                                     genericObject.isInstanceOf("MultipleChoice") or \
                                                     genericObject.isInstanceOf("Selection") or \
                                                     (genericObject.isContainer() and len(genericObject.getChildren()) > 0)
                
        wizardStep1Definition = WizardStepDefinition("Components for Summary Report", "Choose the component that you would like to appear in your summary report.")
        wizardStep1Definition.addPropertyDefinition(PropertyDefinition(identity="components", 
                                                                       displayName="Assessment Components For Summary Report", 
                                                                       defaultValue=[], 
                                                                       description="Please select the components for which you would like to export assessment values.", 
                                                                       propertyType=pt_NATIVE, 
                                                                       propertyStructure=ps_LIST, 
                                                                       propertyFormat=pf_GENERIC_OBJECT_CHOOSER, 
                                                                       isSelectableFunction=isSelectableFunction, 
                                                                       isDisplayableFunction=isDisplayableFunction, 
                                                                       expandAll=true, 
                                                                       containerSelectionSelectsAll=true))

        wizardStep2Definition = WizardStepDefinition("Trend Definition", "The commencement, duration and size of each period to summarize.")
        wizardStep2Definition.addPropertyDefinition(PropertyDefinition(identity="periodSize", 
                                                                       displayName="Time Period Size", 
                                                                       description="The size of each time period in the report", 
                                                                       propertyType=pt_STRING, 
                                                                       propertyFormat=pf_NATIVE, 
                                                                       hasEnumeratedValues=true, 
                                                                       enumeratedValuesMapping=[("Weekly", "weekly"),("Monthly", "monthly"),("Quarterly", "quarterly"),("Yearly", "yearly")], 
                                                                       defaultValue="monthly"))
                                                                       
        wizardStep2Definition.addPropertyDefinition(PropertyDefinition(identity="nrPeriods", 
                                                                       displayName="Number of Time Periods", 
                                                                       description="How many time periods should appear in the report", 
                                                                       propertyType=pt_INTEGER, 
                                                                       propertyFormat=pf_NATIVE, 
                                                                       hasEnumeratedValues=true, 
                                                                       enumeratedValuesList=range(2,53), 
                                                                       defaultValue=4))
                                                                       
        wizardStep2Definition.addPropertyDefinition(PropertyDefinition(identity="dateFrom", 
                                                                       displayName="Start At", 
                                                                       description="The starting date for the report (inclusive)", 
                                                                       defaultValue=None, 
                                                                       propertyType=pt_DATE, 
                                                                       propertyFormat=pf_NATIVE, 
                                                                       propertyStructure=ps_ATOMIC, 
                                                                       hasEnumeratedValues=false))        
                
        return [wizardStep1Definition, wizardStep2Definition]
        
    #---------------------------------------------------------------#

    def renderReport(self, request, wizardState, **args):
        """
            Renders the report content (called by Report.generate)
            
            NOTE: Override this method to perform specific
            report generation/rendering using the provided
            parameters
        """

        #get the assessment storage service... we'll need that to manage the assessment
        assessmentStorageService = getAssessmentStorageService(self)

        #get the current user that is rendering the report
        user = request["USER"]

        #get the assessment filter to use to filter out assessments from the session 
        #(this is typically set by the assessment explorer)
        assessmentFilter = wizardState.getPropertyValue("assessmentFilter")

        #add the assessment filter to the request
        request['ASSESSMENT_FILTER'] = assessmentFilter

        #fetch the assessments
        assessments = assessmentStorageService.getAssessmentsSatisfyingFilter(assessmentFilter, user, sortBy="dateCreated")
        request.set('ASSESSMENTS', assessments)

        #remove the model from the selected components (if it has been selected)
        components = wizardState.getPropertyValue("components")
        components = [component for component in components if component != self.getModel().getIdentity()]

        #order the components as they appear in the model
        components = [genericObject.getIdentity() for genericObject in self.getModel().getDescendantsByOrder() if genericObject.getIdentity() in components]

        wizardState.setPropertyValue("components", components)

        return eval("self.TimeTrendReport_report(self, REQUEST=request, **args)")

    #---------------------------------------------------------------#

    def getMaxValueForComponent(self, assessmentFilters, component, user):
        """
            Helper method to calculate maximum value of a scorable component
            across a series of AssessmentFilters
        """
        
        maxValues = []
        if component.getClassInfo().getClassName() == 'Selection':
            if component.getDisplayFormat() == 'Check Boxes':
                for component in component.getChildren():
                    for assessmentFilter in assessmentFilters:
                        graphable = component.generateGraphableStatistics(assessmentFilter, user)
                        maxValueForAssessmentFilter = max([datum.getValue() for datum in graphable.getDatums()])
                        maxValues.append(maxValueForAssessmentFilter)
                return max(maxValues)                    

        for assessmentFilter in assessmentFilters:
            graphable = component.generateGraphableStatistics(assessmentFilter, user)
            maxValueForAssessmentFilter = max([datum.getValue() for datum in graphable.getDatums()])
            maxValues.append(maxValueForAssessmentFilter)
        return max(maxValues)
        
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation
# Register Class Information with the Knowledge Engine
registerGenericObjectClass(TimeTrendReport)
#####################################################################
